using System;
using System.Collections.Generic;
using GGEditor.API;
using GGEditor.API.Commands;
using GGEditor.API.Editors;
using GGEditor.API.Plugins;

namespace GGEditor.Core.Editors
{
	public class EditorManager
	{
		private readonly IDictionary<string, IEditorDefinition> editorDefinitions =
			new Dictionary<string, IEditorDefinition>();

		protected void Register(IEditorDefinition editorDefinition)
		{
			editorDefinitions[editorDefinition.Name] = editorDefinition;
		}

		protected void Unregister(IEditorDefinition editorDefinition)
		{
			editorDefinitions.Remove(editorDefinition.Name);
		}

		public IEnumerable<string> EditorDefinitions
		{
			get
			{
				foreach (string editorName in editorDefinitions.Keys)
				{
					yield return editorName;
				}
			}
		}

		public IEditor CreateEditor(string editorName)
		{
			IEditorDefinition editorDefinition = editorDefinitions[editorName];

			IEditorModel model = (IEditorModel)Activator.CreateInstance(editorDefinition.ModelType);

			return CreateEditor(editorDefinition, model);
		}

		public IEditor CreateEditor(IEditorModel model)
		{
			foreach (IEditorDefinition definition in editorDefinitions.Values)
			{
				if (definition.ModelType.IsAssignableFrom(model.GetType()))
					return CreateEditor(definition, model);
			}

			throw new ArgumentException();
		}

		public IEditor CreateEditor(IEditorDefinition editorDefinition, IEditorModel model)
		{
			Editor editor = new Editor()
			{
				Title = editorDefinition.Text
			};

			editor.Model = model;
			editor.View = (IEditorView)Activator.CreateInstance(editorDefinition.ViewType);

			Type modelCommandManagerType = typeof(CommandManager<>).
				MakeGenericType(editorDefinition.ModelType);
			object commandManager = Activator.CreateInstance(
				modelCommandManagerType, editor.Model);
			editor.Controller = (IEditorController)Activator.CreateInstance(
				editorDefinition.ControllerType, commandManager);

			editor.UndoManager = commandManager as IUndoManager;

			editor.Controller.SubscribeTo(editor.View);
			editor.View.SubscribeTo(editor.Model);

			return editor;
		}

		#region XmlNodeObjectFactory

		[XmlNodeObjectFactory(Name = "EditorManager")]
		public class EditorManagerFactory : IXmlNodeObjectFactory
		{
			#region Implementation of IXmlNodeObjectFactory

			public object Build(IPluginNode pluginNode, IDictionary<string, string> attributes)
			{
				pluginNode.ChildAdded += OnChildAdded;
				pluginNode.ChildRemoved += OnChildRemoved;
				return Instance;
			}

			#endregion

			void OnChildAdded(IPluginNode node, IPluginNode child, int position)
			{
				Instance.Register(child.Object as IEditorDefinition);
			}

			void OnChildRemoved(IPluginNode node, IPluginNode child, int position)
			{
				Instance.Unregister(child.Object as IEditorDefinition);
			}
		}

		#endregion

		#region Singleton

		private EditorManager() { }

		private static EditorManager instance = new EditorManager();

		public static EditorManager Instance
		{
			get { return instance; }
		}

		#endregion
	}
}